#pragma once

#include "Common.hpp"
#include "PageCache.hpp"

class CentralCache
{
public:
    static CentralCache *GetInstance()
    {
        return &_instance;
    }
    size_t FetchRangeObj(void *&start, void *&end, size_t size, size_t batchnum) // CentralCache从自己的SpanList中获取空间提供给ThreadCache
    {                                                                            /*start end都是输出型参数
                                                                                   size表示申请的块的大小
                                                                                   num表示申请块的数量
                                                                                   目标要求的空间大小：size*num
                                                                                   返回值表示真实提供的空间块数*/
        cout << "debug CentralCache FetchRangeObj begin" << endl;
        size_t index = SizeClass::Index(size); // 获取size对应的下标

        // 此时要对spanlist上的freelist的哈希桶进行操作了，所以需要上锁
        _spanlists[index]._mtx.lock();
        // cout << "debug CentralCache FetchRangeObj lock suc" << endl;
        Span *span = GetOneSpan(_spanlists[index], size);
        span->_is_use = true; // 修改span的状态
        // cout << "debug CentralCache FetchRangeObj GetOneSpan suc" << endl;

        assert(span);            // span不能为nullptr
        assert(span->_freelist); // span的freelist也不能为nullptr

        // 在上面就已经对span->freelist进行了判断
        // 不为空，说明这个span->freelist至少有一个空间块，所以actual_num初始值为1
        size_t actual_num = 1;
        start = end = span->_freelist; // 最开始的时候，start和end都指向span->freelist，也就是最开始处

        size_t i = 0;
        while (i < batchnum - 1 /*end向后移动batchnum，此时start和end之间就刚好有batchnum块空间块*/
               && ObjNext(end) != nullptr)
        {
            end = ObjNext(end);
            actual_num++; // 记录真实提供的空间块
            i++;
        }
        span->_freelist = ObjNext(end); // 先让freelist指向end的后一块
        ObjNext(end) = nullptr;         // 再让end指向nullptr

        span->_use_count += actual_num; // 新分配了空间块出去，此时span里的n就要加上这部分分配出去的空间的数量

        // 为了防止之后忘记解锁，建议写完上锁就把解锁的操作做完
        _spanlists[index]._mtx.unlock();

        cout << "debug CentralCache FetchRangeObj end" << endl;
        return actual_num; // 返回真实提供的空间块的数量
    }

    void ReleaseListToSpans(void *&start, size_t size) // 将回收的空间放入到Span中
    {
        // cout << "CentralCache::ReleaseListToSpans begin" << endl;

        size_t index = SizeClass::Index(size); // 计算出index

        _spanlists[index]._mtx.lock(); // 对CentralCache的哈希桶进行操作的时候，需要上桶锁

        while (start)
        {
            void *next = ObjNext(start); // 记录下一个空间

            Span *span = PageCache::GetInstance()->MapObjectToSpan(start); // 通过页号找到对应的span

            ObjNext(start) = span->_freelist;
            span->_freelist = start;

            span->_use_count--;        // 因为归还了一块空间，所以需要span的usecount需要--
            if (span->_use_count == 0) // 假如此时为0，说明该空间回收完毕，这时候就需要将这部分空间归还给PageCache用于合成更大的页
            {
                _spanlists[index].Erase(span);
                span->_freelist = nullptr; // 因为合成页的操作不需要整块空间中零碎的空间，只需要拿到整块空间就行了
                span->_prev = nullptr;
                span->_next = nullptr;

                _spanlists[index]._mtx.unlock(); // 这时候下面的操作就跟这个桶没有关系了，所以在这里进行解锁的操作

                PageCache::GetInstance()->_mtx.lock();
                PageCache::GetInstance()->ReleaseSpanToPageCache(span);
                PageCache::GetInstance()->_mtx.unlock();

                _spanlists[index]._mtx.lock(); // 重新上锁，以免二次解锁导致出现问题
            }

            start = next;
        }

        _spanlists[index]._mtx.unlock(); // 解锁

        // cout << "CentralCache::ReleaseListToSpans endl" << endl;
    }

public:
    Span *GetOneSpan(SpanList &span_list, size_t size)
    {
        // cout << "debug CentralCache FetchRangeObj GetOneSpan begin" << endl;
        Span *it = span_list.Begin();
        while (it != span_list.End()) // 通过遍历spanlist的方式来寻找spanlist上非空的span
        {
            if (it->_freelist != nullptr) // 此时找到了一个非空的span，所以将这个span返回
                return it;
            // 到下面这里说明当前还没有找到
            it = it->_next; // 所以将这个span节点向后移动
        }

        // 解掉桶锁，让别的线程也能拿到这个桶锁
        span_list._mtx.unlock();
        // cout << "debug CentralCache FetchRangeObj GetOneSpan unlock suc" << endl;
        // 此时，说明遍历完spanlist后也没有找到非空的span
        // 所以这时候应该向PageCache中申请span
        size_t k = SizeClass::NumMovePage(size); // 这里得出所需要的页的数量

        // 这样的方法可以避免在第三种情况，发生递归时产生死锁的问题！！！
        PageCache::GetInstance()->_mtx.lock();
        Span *span = PageCache::GetInstance()->NewSpan(k); // 通过NewSpan这个接口来得到非空的span
        span->_is_use = true;
        span->_obj_size = size;
        PageCache::GetInstance()->_mtx.unlock();
        // cout << "debug CentralCache FetchRangeObj GetOneSpan unlock suc" << endl;
        // 当前这个span还是处于一个未划分过的状态

        // 开始切割这部分空间

        char *start = (char *)(span->_page_id << PAGE_SHIFT);   // 将start指向这份空间的起始位置
        char *end = (char *)(start + (span->_n << PAGE_SHIFT)); // 将end  指向这份空间的末尾位置

        // cout << "debug CentralCache FetchRangeObj GetOneSpan test" << endl;

        span->_freelist = start; // 将这份空间放入到span中进行管理

        void *tail = start; // 将tail指向start这个位置
        start += size;      // 先将start向后移动size个位置，便于控制

        while (start != end) // 用链表将这些空间串起来
        {
            ObjNext(tail) = start; // 将指向tail的指针指向下一个位置
            start += size;         // 将start向后移动到下一个位置
            tail = ObjNext(tail);  // 将tail 向后移动到下一个位置
        }
        // 最后需要这个链表需要指向一个空位置
        ObjNext(tail) = nullptr;

        // cout << "debug CentralCache FetchRangeObj GetOneSpan lock test" << endl;
        // 在这里再重新加一次锁，否则对一个锁进行解两次锁的行为会导致段错误
        span_list._mtx.lock();
        // cout << "debug CentralCache FetchRangeObj GetOneSpan lock suc" << endl;

        // 这里获得到的span是一个没有被管理的span，所以需要把这个span放到cc对应的哈希桶span_list中
        span_list.PushFront(span);
        // cout << "debug CentralCache FetchRangeObj GetOneSpan end" << endl;
        return span;
    }

private:
    CentralCache() {}                                           // 默认构造函数私有化
    CentralCache(const CentralCache &copy) = delete;            // 拷贝构造函数删除
    CentralCache &operator=(const CentralCache &copy) = delete; // 复制拷贝删除
private:
    SpanList _spanlists[FREE_LIST_NUM]; // 因为ThreadCache和CentralCache的对齐规则是相同的，所以这个链表的上限和ThreadCache中的freelist也是相同的
    static CentralCache _instance;      // 设置一个单例模式，因为一个进程中只有一个central_cache
};

CentralCache CentralCache::_instance; // 初始化CentralCache的单例